﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Universe.MySqlTests
{
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Threading;

    using Dapper.Contrib;

    using global::Dapper;

    using LinqToDB;
    using LinqToDB.Data;

    using MySql.Data.MySqlClient;

    using NUnit.Framework;


    [TestFixture]
    public class SimpleTests : BaseTest
    {
        [Test]
        public void Test_1_Master_Connection_Is_Success()
        {
            using (var con = MyContext.MasterConnection)
            {
                for (int i = 0; i <= 1; i++)
                {
                    Stopwatch at = Stopwatch.StartNew();

                    {
                        Trace.WriteLine("  " + at.Elapsed + " MySQL Server Version: " + MySqlExtentions.GetVersionString(con));
                    }
                }

                string version; // null at 5.1
                if (con.HasInnoDb(out version))
                    Trace.WriteLine("  InnoDB is supported. " + (version == null ? "" : "Version: " + version + "; ")  + "Default Engine is " + con.DefaultEngine());
                else
                    Trace.WriteLine("  InnoDB isn't supported; Default Engine is " + con.DefaultEngine());

                MySqlExtentions.TraceCharset(con);
            }
        }

        [Test]
        public void Test_2_List_Databases()
        {
            Stopwatch at = Stopwatch.StartNew();
            using (var con = MyContext.MasterConnection)
            {
                var dblist = MySqlExtentions.GetDatabases(con);
                var m1 = at.Elapsed;
                foreach (var db in dblist)
                {
                    Trace.WriteLine(string.Format("  {0:n1} {1}", 
                        string.Format("{0,12}", (db.Data + db.Index).ToString("n0") + " Kb"), 
                        db.Name));
                }
                at = Stopwatch.StartNew();
                dblist = MySqlExtentions.GetDatabases(con);
                var m2 = at.Elapsed;
                Trace.WriteLine(string.Format("  Total: {0:n} Kb; First: {1}; Next: {2}",
                    dblist.Select(x => x.Data + x.Index).Sum(),
                    m1, m2));
            }
        }

        [Test]
        public void Test_3_List_Tables()
        {
            using (var con = MyContext.MasterConnection)
            {
                var dblist = MySqlExtentions.GetDatabases(con);
                foreach (var db in dblist)
                {
                    var tables = MySqlExtentions.GetTables(con, db.Name);
                    long size = tables.Select(x => x.Data + x.Index).Sum();
                    Trace.WriteLine("  " + db.Name + ": " + tables.Count + " tables and views, " + size.ToString("n0") + " Kb");
                }

                var allTables = MySqlExtentions.GetTables(con);
                long allSize = allTables.Select(x => x.Data + x.Index + x.Free).Sum();
                Trace.WriteLine("  " + "TOTAL" + ": " + allTables.Count + " tables and views, Size: " + allSize.ToString("n0") + " Kb");

            }
            
        }

        [Test]
        public void Test_4_Check_Schema()
        {
            MyContext.CheckSchema();
        }

        [Test]
        public void Test_5_MyTable_Info()
        {
            using(var con = MyContext.MasterConnection)
                MyContext.ShowMyTableInfo(con);
        }

        [Test]
        public void Test_6A_Insert_1_Row_Using_Dapper()
        {
            GenerateMyEntities(rows: 1, bytes: 3).First();
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                for (int i = 1; i <= 2; i++)
                {
                    var at = Stopwatch.StartNew();
                    var my = GenerateMyEntities(rows: 1, bytes: 3).First();
                    my.Para = "First Row! :)";

                    MyContext.Insert(con, new[] {my,});
                    Trace.WriteLine(string.Format("{0}: {1}",
                        i == 1 ? "First" : " Next",
                        at.Elapsed));
                }
            }
        }

        [Test]
        public void Test_6B_Insert_1_Row_Using_Linq2DB()
        {
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                for (int i = 1; i <= 2; i++)
                {
                    var at = Stopwatch.StartNew();
                    var my = GenerateMyEntities(rows: 1, bytes: 3).First();
                    my.Para = "First Row! :)";

                    using (DbLinq db = new DbLinq(con))
                        db.Insert(my);

                    Trace.WriteLine(string.Format("{0}: {1}",
                        i == 1 ? "First" : " Next",
                        at.Elapsed));
                }
            }
        }




        [Test, Explicit]
        public void Test_6C_Insert_1_Row_Using_CRUD()
        {
            SimpleCRUD.SetDialect(SimpleCRUD.Dialect.MySql);
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                for (int i = 1; i <= 2; i++)
                {
                    var at = Stopwatch.StartNew();
                    var my = GenerateMyEntities(rows: 1, bytes: 3).First();
                    my.Para = "First Row! :)";

                    con.Insert<MyTable>(my);

                    Trace.WriteLine(string.Format("{0}: {1}",
                        i == 1 ? "First" : " Next",
                        at.Elapsed));
                }
            }
        }

        [Test]
        public void Test_7A_Insert_1000_Row()
        {

            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                var at = Stopwatch.StartNew();
                const int rows = 1000;
                MyContext.Insert(con, GenerateMyEntities(rows, bytes: 3));
                Trace.WriteLine(string.Format("{0} rows: {1}",
                    rows,
                    at.Elapsed));

                MyContext.ShowMyTableInfo(con);
            }
        }

        [Test]
        public void Test_7B_Insert_1000_Row_using_Linq2DB()
        {

            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                var at = Stopwatch.StartNew();
                const int rows = 1000;
                using (DbLinq db = new DbLinq(con))
                    db.BulkCopy(100, GenerateMyEntities(rows, bytes: 3));

                Trace.WriteLine(string.Format("{0} rows: {1}",
                    rows,
                    at.Elapsed));

                MyContext.ShowMyTableInfo(con);
            }
        }


        [Test]
        public void Test_8_Insert_1_Row_SMARTY()
        {
            using (var con = MyContext.WorkingConnection)
            {
                for (int i = 1; i <= 2; i++)
                {
                    var my = GenerateMyEntities(1, bytes: 3).First();
                    var at = Stopwatch.StartNew();
                    MyContext.InsertSmarty(con, new[] {my,});
                    Trace.WriteLine(string.Format("  1 row SMARTY {0}: {1}",
                        i == 1 ? "first" : " next",
                        at.Elapsed));
                }

                MyContext.ShowMyTableInfo(con);
            }
        }


        [Test]
        public void Test_9_Insert_1000_Row_SMARTY()
        {
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                var at = Stopwatch.StartNew();
                int rows = 1000;
                MyContext.InsertSmarty(con, GenerateMyEntities(rows, bytes: 3));
                Trace.WriteLine(string.Format("{0} rows SMARTY: {1}",
                    rows,
                    at.Elapsed));

            }

            using (var con = MyContext.MasterConnection)
                MyContext.ShowMyTableInfo(con);
        }

        [Test, Explicit]
        public void Test_A1_Insert_1_Row_Via_EntityFramework()
        {
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                for (int i = 1; i <= 2; i++)
                {
                    bool isFirst = i == 1;
                    var my = GenerateMyEntities(rows: 1, bytes: 3).First();
                    var at = Stopwatch.StartNew();
                    using (MyContext ctx = new MyContext(con, false))
                    {
                        ctx.Database.Log += s =>
                        {
                            if (isFirst) Trace.WriteLine("[EF]: " + s);
                        };

                        ctx.MyTableSet.Add(my);
                        ctx.SaveChanges();
                    }

                    Trace.WriteLine(string.Format("  Insert 1 row via EntityFramework. {0}: {1}",
                        i == 1 ? "first" : " next",
                        at.Elapsed));
                }
            }

            using (var con = MyContext.MasterConnection)
                MyContext.ShowMyTableInfo(con);
        }





        // Before: 00:05:29.5536221 at 100 Kbytes per row
        // Before: 00:00:11.6719563 at   3 bytes  per row
        [Test] 
        public void Test_A2_Insert_100_Row_Each_100Kb_SMARTY()
        {
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                var at = Stopwatch.StartNew();
                int rows = 100;
                MyContext.InsertSmarty(con, GenerateMyEntities(rows: rows, bytes: 100*1024));
                Trace.WriteLine(string.Format("{0} rows SMARTY: {1}",
                    rows,
                    at.Elapsed));
            }

            using (var con = MyContext.MasterConnection)
            MyContext.ShowMyTableInfo(con);
        }

        [Test]
        public void Test_A3_Select_Nothing()
        {
            using (MySqlConnection con = MyContext.WorkingConnection)
            {
                var list = SqlMapper.Query<MyTable>(con, "Select * From MyTable Where Id = @Id;", new {Id = Guid.NewGuid().ToByteArray()}).ToList();
                Assert.IsEmpty(list);
            }
        }

        [Test]
        public void Test_A4A_Select_Nothing_Stress()
        {
            ThreadLocal<MySqlConnection> con = new ThreadLocal<MySqlConnection>(() =>
            {
                return MyContext.WorkingConnection;
            });

            Action finalizer = () => { con.Value.Close(); };
            var param = new { Id = Guid.NewGuid().ToByteArray() };

            Action iteration = () =>
            {
                var list = SqlMapper.Query<MyTable>(
                    con.Value,
                    "Select * From MyTable Where Id = @Id;", param
                    ).ToArray();
            };

            Action initializer = () =>
            {
                iteration();
            };

            foreach (var threads in new[] { 1, 2, 4, 8 })
            {
                UnitStress.Run(threads, 3000, iteration, initializer, finalizer);
            }
        }
        
        [Test]
        public void Test_A4B_Select_Null_Stress()
        {
            ThreadLocal<MySqlConnection> con = new ThreadLocal<MySqlConnection>(() =>
            {
                return MyContext.WorkingConnection;
            });

            Action iteration = () =>
            {
                var @null = SqlMapper.Query(
                    con.Value,
                    "Select Null;"
                    ).FirstOrDefault();
            };

            Action initializer = () =>
            {
                iteration();
            };

            Action finalizer = () => { con.Value.Close(); };
            foreach (var threads in new[] { 1, 2, 4, 8 })
            {
                UnitStress.Run(threads, 3000, iteration, initializer, finalizer);
            }
        }

        [Test]
        public void Test_A5_Insert_1_Row_Stress()
        {
            ThreadLocal<MySqlConnection> con = new ThreadLocal<MySqlConnection>(() =>
            {
                return MyContext.WorkingConnection;
            });

            Action iteration = () =>
            {
                MyContext.InsertSmarty(con.Value, GenerateMyEntities(rows: 1, bytes: 1));
            };

            Action initializer = () =>
            {
                con.Value.Query("Select Null;").FirstOrDefault();
            };

            Action finalizer = () => { con.Value.Close(); };
            foreach (var threads in new[] { 1, 2, 4, 8 })
            {
                UnitStress.Run(threads, 3000, iteration, initializer, finalizer);
            }
        }

        private static long _count;
        static IEnumerable<MyTable> GenerateMyEntities(int rows = 1000, int bytes = 3)
        {
            byte[] bin = new byte[bytes];
            for (int i = 0; i < 5 && i < bytes; i++)
                bin[i] = (byte) (90 + i);
                
            for (int i = 1; i <= rows; i++)
            {
                yield return new MyTable()
                {
                    Id = Guid.NewGuid().ToByteArray(),
                    Created = DateTime.Now,
                    Enabled = true,
                    Para = "Next Row! " + Interlocked.Increment(ref _count) + " :)",
                    Payload = bin,
                };
            }
        }

        [TestFixtureTearDown]
        public void MyTearDown()
        {
            using (var con = MyContext.MasterConnection)
                MyContext.ShowMyTableInfo(con);

        }



    }

}
